package com.woniuxy.woniufp.system.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import com.woniuxy.woniufp.common.model.constant.RedisConstant;
import com.woniuxy.woniufp.common.model.constant.UserConstant;
import com.woniuxy.woniufp.common.model.dto.bank.BankCardAddDTO;
import com.woniuxy.woniufp.common.model.dto.bank.CardBindDTO;
import com.woniuxy.woniufp.common.model.dto.bank.SignatureDTO;
import com.woniuxy.woniufp.common.model.dto.user.UserLoginDto;
import com.woniuxy.woniufp.common.model.dto.user.UserRegistDto;
import com.woniuxy.woniufp.common.model.exceptions.Assert;
import com.woniuxy.woniufp.common.model.result.enums.impl.BusinessCode;
import com.woniuxy.woniufp.common.model.utils.JwtUtils;
import com.woniuxy.woniufp.common.model.utils.TokenUtiles;
import com.woniuxy.woniufp.common.model.utils.UniqueIDGenerator;
import com.woniuxy.woniufp.common.model.vo.user.TokenVo;
import com.woniuxy.woniufp.common.model.vo.user.UserInfoVo;
import com.woniuxy.woniufp.common.model.vo.user.UserLoginVo;
import com.woniuxy.woniufp.system.client.BankFeignClient;
import com.woniuxy.woniufp.system.entity.User;
import com.woniuxy.woniufp.system.entity.UserCard;
import com.woniuxy.woniufp.system.entity.UserDetail;
import com.woniuxy.woniufp.system.mapper.UserDetailMapper;
import com.woniuxy.woniufp.system.mapper.UserMapper;
import com.woniuxy.woniufp.system.service.IUserCardService;
import com.woniuxy.woniufp.system.service.IUserService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 用户业务操作的实现类
 */
@Service
@Slf4j
public class UserServiceImpl implements IUserService {
    @Autowired
    UserMapper userMapper ;

    @Autowired
    UserDetailMapper userDetailMapper ;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Autowired
    StringRedisTemplate stringRedisTemplate ;

    @Autowired
    TransactionTemplate transactionTemplate ;

    @Autowired
    JwtUtils jwtUtils ;

    @Autowired
    IUserCardService userCardService ;

    @Autowired
    BankFeignClient bankFeignClient ;

    /**
     * 用户注册
     * @param userRegistDto
     */
    @Override
    @Transactional  //默认抛出 RuntimeException  就回滚
    public void regiter(UserRegistDto userRegistDto) {
        //userRegistDto.getPhone().intern()  可以防止 想通的手机 获得锁
        //synchronized (userRegistDto.getPhone().intern()){
            //begin
            //1. 验证手机号 是否被注册过
            Long count = userMapper.isExitPhone(userRegistDto.getPhone());
            //commit
            //断言判断  手机好是否被注册过
            Assert.error(count > 0, BusinessCode.PHONE_EXISTED);

            //2.保存用户信息
            saveUserInfo(userRegistDto);
        //}
    }
    //保存用户信息
    private void saveUserInfo(UserRegistDto userRegistDto) {
        //创建一个User 对象
        User user = new User();
        user.setPhone(userRegistDto.getPhone());
        user.setUserName(userRegistDto.getUserName());
        user.setPwd(passwordEncoder.encode(userRegistDto.getPwd()));  // 密码  使用密文
        user.setUserType(UserConstant.USER_TYPE_PERSONAL);  // 1 个人用户   0  企业用户
        user.setUserStatus(1);
        userMapper.insert(user);  //

        //int i  = 10 / 0 ;

        UserDetail userDetail = new UserDetail();
        userDetail.setId(user.getId());
        userDetail.setUserName(userRegistDto.getUserName());
        userDetail.setPhone(userRegistDto.getPhone());
        userDetail.setBorrowMoney(new BigDecimal("0"));
        userDetail.setCardBindStatus(0);
        userDetail.setUserCode(UniqueIDGenerator.generator(UserConstant.USER_CODE_PREFIX));
        userDetailMapper.insert(userDetail);
    }

    @Override
   // @Transactional
    public UserLoginVo login(UserLoginDto userLoginDto) {
        //判断是什么登录
        boolean phoneLogin = userLoginDto.isPhoneLogin();

       /* if (phoneLogin) {
            return  phoneLogin(userLoginDto.getLoginName(),userLoginDto.getLoginSecret());
        }else {
            return uplogin(userLoginDto.getLoginName(),userLoginDto.getLoginSecret());
        }*/
        return phoneLogin == true ? phoneLogin(userLoginDto.getLoginName(),userLoginDto.getLoginSecret())
                : uplogin(userLoginDto.getLoginName(),userLoginDto.getLoginSecret());
    }



    //账号登录
    private UserLoginVo uplogin(String loginName, String loginSecret) {
        // 1.根据用户名 查询 用户信息
        User user = userMapper.selectUserByName(loginName);
        //判断 user 是否为空
        Assert.error(user,BusinessCode.USER_NOT_EXIST);
        //判断密码是否正确
        Assert.error(!passwordEncoder.matches(loginSecret,user.getPwd()),BusinessCode.LOGIN_FAIL);

        //封装返回值
        return getLoginVo(user);
    }

    private UserLoginVo getLoginVo(User user) {
        UserLoginVo userLoginVo = BeanUtil.copyProperties(user,UserLoginVo.class);
        //生成 token  存储到 UserLoginVo 对象
        Map<String,Object> maps = new HashMap<>();
        maps.put(UserConstant.TOKEN_USERID,user.getId());
        maps.put(UserConstant.TOKEN_USERNAME,user.getUserName());
        maps.put(UserConstant.TOKEN_PHONE,user.getPhone());

        String token = jwtUtils.createToken(maps);
        userLoginVo.setToken(token);

        ////把token存储到redis中
       stringRedisTemplate.opsForValue().set(
       RedisConstant.USER_TOKEN+user.getId(), token, 30, TimeUnit.MINUTES);

        return userLoginVo;
    }

    /**
     * isPhoneLogin为false，表示根据用户名密码登录
     * 实现：校验用户名密码是否存在
     * isPhoneLogin为true，表示根据手机号验证码登录
     * 实现：
     *      1、访问短信验证码接口，实现验证码发送 （sddfp-oss）
     *      2、校验验证码：获取redis中的验证码与用户输入的验证码对比
     *      3、手机号不存在自动注册功能：随机生成6位用户名、密码默认
     *      4、手机号存在则返回
     */
    private UserLoginVo phoneLogin(String phone, String code) {
        //查询一下验证码
        String redisCode = stringRedisTemplate.opsForValue().get(RedisConstant.REDIS_SMS_PREFIX+phone);
        //判断验证码是否村粗在
        Assert.error(redisCode,BusinessCode.USER_VERIFY_CODE_ERROR);
        // 查看验证码是否错误
        Assert.error(!redisCode.equals(code),BusinessCode.USER_VERIFY_CODE_ERROR);
        //1. 验证 手机号是否注册过
        User user = userMapper.selectUserByPhone(phone);
        if (user == null){
            //没有注册过
            UserRegistDto userRegistDto = new UserRegistDto();
            userRegistDto.setPhone(phone);
            userRegistDto.setPwd("123456");
            userRegistDto.setUserName(RandomUtil.randomString(6));

            //注册 处理局部事务
            transactionTemplate.execute((status -> {
                this.regiter(userRegistDto);
                return new Object();
            }));
            //查询一次
            user = userMapper.selectUserByPhone(phone);
        }

        UserLoginVo loginVo = getLoginVo(user);
        //删除验证码
        stringRedisTemplate.delete(RedisConstant.REDIS_SMS_PREFIX+phone);
        return loginVo;
    }

    /**
     * 用户绑卡的业务
     * 1、 检查用户是否已经关联银行卡
     * 2、 判断银行卡是否已被绑定
     * 3、 更新用户的信息
     * 3.1 更新用户信息：根据传入的参数更新真实姓名、身份证
     * 3.2 更新用户明细：生成流水号并更新
     * 4、 用户绑定银行卡（u_user_card），如已经绑定过不能重复绑定
     * 5、 发送远程调用请求，发送数据到银行托管代理服务
     * @param cardBindDTO
     * @return
     */
    @Override
    @GlobalTransactional    //全局事务
    public SignatureDTO createConsumer(CardBindDTO cardBindDTO) {
        //1. 获取当前登录用户信息 （id、phon、userName） 从请求头中 获取token ，token中 包含用户信息
        TokenVo tokenVo = TokenUtiles.getTokenVo();
        //2.1 判断用户表（u_user_detail）中 当前用户是否已经绑卡(CARD_BIND_STATUS)
        UserDetail userDetail = userDetailMapper.selectByPrimaryKey(tokenVo.getId());
        Assert.error(userDetail.getCardBindStatus()==1,BusinessCode.USER_CARD_ALREADY_BINDED);
        //2.2 验证用户绑卡表 中有没有此卡 （u_user_Card）
        UserCard userCard = userCardService.findUserCardByPhone(tokenVo.getPhone());
        Assert.error(userCard != null,BusinessCode.USER_CARD_ALREADY_BINDED);

        //3.如果没有绑卡 插入一条用户的绑卡表 (u_user_card)
        userCard = new UserCard();
        userCard.setUserId(Long.parseLong(tokenVo.getId()));
        userCard.setCardNo(cardBindDTO.getCardNo());
        userCard.setPhone(tokenVo.getPhone());
        userCard.setStatus(true);
        //保存
        userCardService.saveuserCard(userCard);
        //4.1 更新用户的信息 真实姓名 、身份证号 (u_user)
        //  update u_user set real_name = '张三丰' ,identity_code = '5666565565651' where id = 1 ;
        User user = userMapper.selectByPrimaryKey(tokenVo.getId());
        user.setRealName(cardBindDTO.getRealName());
        user.setIdentityCode(cardBindDTO.getIdentityCode());
        userMapper.updateByPrimaryKey(user);
        //4.2 、更新用户明细表 生成 请求流水号 (u_user_detail)
            // 生成一个请求 流水号  使用UUID + 前缀
        // update u_user_detail set req_code = 'REQW_dsdsfdfsdfsdfsfsdf' where id = 1 ;
        UserDetail userDetail1 = userDetailMapper.selectByPrimaryKey(tokenVo.getId());
        // 更新请求流水号
        userDetail1.setReqCode(UniqueIDGenerator.generator(UserConstant.REQUEST_CODE_PREFIX));
        userDetailMapper.updateByPrimaryKey(userDetail1) ;

        //5 .访问 银行托管代理系统   代理系统访问 银行托管系统
        BankCardAddDTO bankCardAddDTO = new BankCardAddDTO();
        bankCardAddDTO.setUserId(user.getId());
        bankCardAddDTO.setReqCode(userDetail1.getReqCode());
        // OpenFign 远程调用  没有走 网关
        SignatureDTO signatureDTO = bankFeignClient.proxyBindCardLog(bankCardAddDTO);
        //绑卡失败，抛出异常
        Assert.error(signatureDTO,BusinessCode.USER_CARD_BINDED_FEIGN_ERROR);

        return signatureDTO;
    }

    @Override
    public UserInfoVo findById(Long userId) {
        User user = userMapper.selectByPrimaryKey(userId);
        UserDetail userDetail = userDetailMapper.selectByPrimaryKey(userId);
        //UserInfoVo userInfoVo = new UserInfoVo();
        UserInfoVo userInfoVo = BeanUtil.copyProperties(user,UserInfoVo.class);
        userInfoVo.setUserCode(userDetail.getUserCode());
        return userInfoVo;
    }

}
